/**
 *  @file  vsLogger.h version 1.0 2007-11-20 
 *  @brief Define class Logger.
 *  Copyright (c) 2007 by @author <abitfree@gmail.com>
 */

#ifndef __VS_LOGGER_H__
#define __VS_LOGGER_H__

#include <list>
#include <cstdarg>
#include <algorithm>
#include <ctime>
#include "vsLogOutput.h"
#include "vsStdOutput.h"
#include "vsTimeRollingOutput.h"
#include "vsFixedSizeFileOutput.h"

#ifdef WIN32
#include <windows.h>	// for LARGE_INTEGER
#endif

namespace vs {
namespace log {

/// The log level enum data type.
enum E_LOG_LEVEL
{
	LOG_DEBUG = 0,	///< The DEBUG level, output information for debugging.
	LOG_INFO,		///< The INFO level, output information for business.
	LOG_WARN,		///< The WARN level, output information for warning, often when something is abnormal.
	LOG_ERROR,		///< The ERROR level, output information for errors, often when some application errors happen.
	LOG_FATAL,		///< The FATAL level, output information for system errors.
	LOG_NOLOG		///< No logging, only to turn off the logging.
};


/**
 * @brief The logging class, which logs data to logging output objects. 
 *
 * The abstraction of the logging class, it can be printed to screen, 
 * or to a file, and so on.
*/
class Logger
{
private:
	/// The max size of the format buffer.
	enum { FORMAT_BUF_SIZE = 256 };

public:
	
	/// The max size of the logging buffer.
	enum { BUF_SIZE = 2048 };

	/// The constructor.
	Logger(E_LOG_LEVEL level = LOG_INFO, const char *pszFormat = 0, LogOutput *pOutput = 0);

	/// The destructor.
	~Logger();

	/// The Open() method sets the status of the logger.
	bool Open(E_LOG_LEVEL level = LOG_INFO, const char *pszFormat = 0, LogOutput *pOutput = 0);

	/// Close the logger, can not log messages any more.
	bool Close();

	//@{
	/// Method for logging messages, its args is printf style. Details 
	void LogMsg(E_LOG_LEVEL eLogLevel, const char *szFormat, ...);
	void LogMsg(E_LOG_LEVEL eLogLevel, const char *szFormat, va_list ap);
	//@}

	/// Set the log level, if log level is less than eLogLevel, it is not write to the log output.
	void SetLogLevel(E_LOG_LEVEL eLogLevel);

	/// Get the log level.
	E_LOG_LEVEL GetLogLevel() const;

	/// Set the log format string, the supported log formats are:
    /// Y(4 digits year), M(2 digits month), D(2 digits date), h(2 digits hour), m(2 digits minute), s(2 digits second), 
    /// T(14 digits timestamp), R(log message), L(long log level name), l(short log level name), p(process), t(thread).
	void SetLogFormat(const char *pszFormat);

	/// Get the log format string.
	const char* GetLogFormat() const;

	/// Set the log target instance, it can be switched by user at any time.
	void AddLogOutput(LogOutput *pOutput);
	
	/// Remove the log target instance.
	bool RemoveLogOutput(LogOutput *pOutput);

	/// Remove the log target instance.
	void RemoveAllLogOutput();
	
	/// Force to write logs to the log output.
	int Flush();
	
protected:
	/**
	 * @brief Parse the log format string, and prepare to output to log target.
	 *
	 * The supported log format is below Y,M,D,h,m,s,T,R,L,l,p,t.
	 * @sa SetLogFormat().
	 */
	int ParseFormat(E_LOG_LEVEL eLogLevel, char *pszRecord, const char *pszMsg, int nMaxLength);

	/// Update the timestamp for a new logging.
	void UpdateTimeStamp();
protected:

	/// The logging level.
	E_LOG_LEVEL m_eLogLevel;

	/** @brief The printf() style logging format, supporting these formatter
	 *  Y,M,D,h,m,s,T,R,L,l,p,t. @sa SetLogFormat().
	 */
	char m_szLogFormat[FORMAT_BUF_SIZE];

#ifdef WIN32
	long double m_dfFreqence;
#else
	struct timeval m_TvNow;
#endif

	// The last logging timestamp.
	struct tm m_TmNow;

	// The logging target, if it is NULL, the logging message will be printed on the standard output.
	//LogOutput *m_pLogOutput;
	std::list<LogOutput*> m_LogOutputs;
};


#define INLINE inline

// implementation for Logger
INLINE
Logger::Logger(E_LOG_LEVEL level /* = LOG_INFO*/, 
				 const char *pszFormat /* = 0*/, 
				 LogOutput *pOutput /* = 0*/)
{
	Open(level, pszFormat, pOutput);
}

INLINE
Logger::~Logger()
{
	Close();
}

INLINE
void Logger::LogMsg(E_LOG_LEVEL eLogLevel, const char *pszFormat, ...)
{
	va_list ap;
	va_start(ap, pszFormat);
	LogMsg(eLogLevel, pszFormat, ap);
	va_end(ap);
}

INLINE
void Logger::SetLogLevel(E_LOG_LEVEL eLogLevel)
{
	m_eLogLevel = eLogLevel;
}

INLINE
E_LOG_LEVEL Logger::GetLogLevel() const
{
	return m_eLogLevel;
}

INLINE
void Logger::SetLogFormat(const char *pszFormat)
{
	if (pszFormat == 0)
		strncpy(m_szLogFormat, "%R", FORMAT_BUF_SIZE-1);
	else
		strncpy(m_szLogFormat, pszFormat, FORMAT_BUF_SIZE-1);
}

INLINE
const char* Logger::GetLogFormat() const
{
	return m_szLogFormat;
}

INLINE
void Logger::AddLogOutput(LogOutput *pOutput)
{
	if ((pOutput != 0) && (pOutput->IsValid()))
	{
		m_LogOutputs.push_back(pOutput);
	}
}

INLINE
bool Logger::RemoveLogOutput(LogOutput *pOutput)
{
	std::list<LogOutput*>::iterator iter = std::find(m_LogOutputs.begin(), m_LogOutputs.end(), pOutput);
	m_LogOutputs.erase(iter);
	return true;
}

INLINE
void Logger::RemoveAllLogOutput()
{
	m_LogOutputs.clear();
}

INLINE
void Logger::UpdateTimeStamp()
{
#ifndef WIN32
	gettimeofday(&m_TvNow, 0);
#endif

	std::time_t tNow;
	tNow = std::time(0);
	std::tm *pTm = std::localtime(&tNow);
	memcpy(&m_TmNow, pTm, sizeof(m_TmNow));
}

INLINE
bool Logger::Open(E_LOG_LEVEL level /* = LOG_INFO*/, 
				   const char *pszFormat /* = 0*/, 
				   LogOutput *pOutput /* = 0*/)
{
	// initialize the frequency for window high performance time counter.
#ifdef WIN32
	LARGE_INTEGER liFreq;
	QueryPerformanceFrequency(&liFreq);
	m_dfFreqence = liFreq.QuadPart;
#endif

	// clear the original log state firstly.
	Close();
	
    SetLogLevel(level);
	SetLogFormat(pszFormat);
	AddLogOutput(pOutput);
    
	return true;
}

INLINE
bool Logger::Close()
{
	SetLogLevel(LOG_INFO);
	SetLogFormat(0);
	
	
	for (std::list<LogOutput*>::iterator iter = m_LogOutputs.begin();
		 iter != m_LogOutputs.end();
		 ++ iter)
	{
		delete (*iter);
	}
	m_LogOutputs.clear();

	return true;
}

INLINE
void Logger::LogMsg(E_LOG_LEVEL eLogLevel, const char *szFormat, va_list ap)
{
	if (eLogLevel < m_eLogLevel)
	{
		return;
	}

	char szMsg[BUF_SIZE] = { 0 };
	int nMsgSize = _vsnprintf(szMsg, BUF_SIZE, szFormat, ap);
	char szFormattedMsg[BUF_SIZE] = { 0 };
	int nFormattedMsgSize = ParseFormat(eLogLevel, szFormattedMsg, szMsg, BUF_SIZE);

	for (std::list<LogOutput*>::iterator iter = m_LogOutputs.begin();
		 (iter != m_LogOutputs.end());
		 ++iter)
	{
		(*iter)->Write(szFormattedMsg, nFormattedMsgSize, &m_TmNow);
	}
}

INLINE
int Logger::ParseFormat(E_LOG_LEVEL eLogLevel, char *pszRecord, const char *pszMsg, int nMaxLength)
{
	// The full name of the log level.
	static const char* g_pszLogLevelFullName[] =
	{
		"DEBUG",
		"INFO",
		"WARN",
		"ERROR",
		"FATAL"
	};

	// The short name of the log level.
	static const char* g_pszLogLevelShortName[] =
	{
		"D",
		"I",
		"W",
		"E",
		"F"
	};

	UpdateTimeStamp();
	const char *src = m_szLogFormat;
	char *dst = pszRecord;

	int nCount = 0;
	int nStep = 0;
	while (*src != '\0')
	{
		if (*src == '%')	// parse the format special character
		{
			++src;
			switch (*src)
			{
			case '%':	// output the '%'
				*dst = *src;
				nStep = 1;
				break;
			case 'Y':	// output the 4 bit year
				nStep = sprintf(dst, "%04d", m_TmNow.tm_year+1900);
				break;
			case 'M':	// output the 2 bit month
				nStep = sprintf(dst, "%02d", m_TmNow.tm_mon+1);
				break;
			case 'D':	// output the 2 bit date
				nStep = sprintf(dst, "%02d", m_TmNow.tm_mday);
				break;
			case 'h':	// output the 2 bit hour
				nStep = sprintf(dst, "%02d", m_TmNow.tm_hour);
				break;
			case 'm':	// output the 2 bit minute
				nStep = sprintf(dst, "%02d", m_TmNow.tm_min);
				break;
			case 's':	// output the 2 bit second
				nStep = sprintf(dst, "%02d", m_TmNow.tm_sec);
				break;
			case 'T':	// output the timestamp
				{
#ifdef WIN32
					LARGE_INTEGER liTimeStamp;
					QueryPerformanceCounter(&liTimeStamp);
					long double nSeconds = liTimeStamp.QuadPart / m_dfFreqence;
					long nMicroSeconds = (nSeconds - long(nSeconds)) * 1000000;
					nStep = sprintf(dst, "%d:%06d", long(nSeconds), nMicroSeconds);
#else
					nStep = sprintf(dst, "%04d%02d%02d%02d%02d%02d:%06d", m_TmNow.tm_year+1900, m_TmNow.tm_mon+1, m_TmNow.tm_mday, 
						m_TmNow.tm_hour, m_TmNow.tm_min, m_TmNow.tm_sec, m_TvNow.tv_usec);
#endif
				}
				break;
			case 'R':	// output the log message
				nStep = sprintf(dst, "%s", pszMsg);
				break;
			case 'L':	// output the full named log level
				nStep = sprintf(dst, "%s", g_pszLogLevelFullName[eLogLevel]);
				break;
			case 'l':	// output the abbreviated named log level
				nStep = sprintf(dst, "%s", g_pszLogLevelShortName[eLogLevel]);
				break;
			case 'p':	// output the process number
				nStep = sprintf(dst, "%ld", GetCurrentProcessId());
				break;
			case 't':	// output the thread number
				nStep = sprintf(dst, "%ld", GetCurrentThreadId());
				break;

			default:	// unsupported format character, just output it
				nStep = 1;
				break;
			}

			++src;

			nCount += nStep;
			dst += nStep;
		}
		else		// normal character
		{
			*dst++ = *src++;
			nCount++;
		}
	}

	return nCount;
}

} // namespace log
} // namespace vs

#endif // __VS_LOGGER_H__
